﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using ExcelDna.Integration;
using Exocortex.Base.Shared;
using Exocortex.Excel.Shared;
using Helpers = Exocortex.Base.Shared.Helpers;

namespace Exocortex.Excel.Nuget
{
    public class NugetFunctions
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
        public const string PackagePath = "packagePath";
        public const string BinDir = "binDir";
        public const string RootPath = "rootPath";

        #region Autoupdate

        [ExcelCommand(MenuName="Exocortex", MenuText="Check for updates...")]
        public static void AutoUpdate()
        {
            foreach (var item in NugetClientAddIn.NugetFeedManager.Clients.Values)
            {
                // Auto-update code
                try
                {
                    var hit = item.GetPackagesWithUpdates("Exocortex").FirstOrDefault();
                    if (hit != null)
                    {
                        var updateVersion = item.GetUpdate(hit).Version.ToString();
                        var message = String.Format("An Exocortex update is available. Server = {0}. Current Version = {1}, New Version = {2}",
                                                    item.SourceRepository.Source, hit.Version, updateVersion);
                        var result = MessageBox.Show(
                            message, 
                            "Exocortex Update",
                            MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
                        if (result == DialogResult.Yes)
                            item.UpdatePackage(hit);
                    }
                }
                catch (Exception e)
                {
                    _logger.ErrorException("Error during update.", e);
                }
            }
        }

        #endregion

        #region Feed management

        /// <summary>
        /// Register a new feed
        /// </summary>
        /// <param name="alias">The alias to associate with this feed</param>
        /// <param name="address">The address of the feed source</param>
        /// <param name="isDefault">(Optional) Set to true to make this the default feed</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "nugetConnectFeed",
            Category = "Nuget (Feeds)",
            Description = "Create an Nuget Feed",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool CreateFeed(string alias, string address, bool isDefault = false)
        {
            // Read paths
            var configFile = Helpers.ConfigFile();
            var defaultPackagePath = Path.GetFullPath(CoreTools.XllPath() + @"\..\package");
            var packagePath = configFile.GetKey(PackagePath, defaultPackagePath);
            var binDir = configFile.GetKey(BinDir, "bin");
            var defaultRoot = Path.GetFullPath(CoreTools.XllPath() + @"\..");
            var rootPath = configFile.GetKey(RootPath, defaultRoot);

            // Register feed
            _logger.Info("Registering feed. Address = {0}, Alias = {1}", address, alias);
            var wpm = new WebProjectManager(address, rootPath, packagePath, binDir);
            NugetClientAddIn.NugetFeedManager.Add(alias, wpm);

            // Make default
            if (isDefault)
                NugetClientAddIn.NugetFeedManager.MakeDefault(alias);

            return true;
        }

        /// <summary>
        /// Set the default feed to use when the feed alias is omitted from a worksheet function.
        /// </summary>
        /// <param name="alias">The new default feed alias.</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "nugetSetDefaultFeed",
            Category = "Nuget (Feeds)",
            Description = "Specify the name of the default Nuget Feed",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static bool SetDefaultFeed(string alias)
        {
            return NugetClientAddIn.NugetFeedManager.MakeDefault(alias);
        }

        /// <summary>
        /// Returns the alias of the default feed.
        /// </summary>
        /// <returns>The alias of the default feed.</returns>
        [ExcelFunction(
            Name = "nugetGetDefaultFeed",
            Category = "Nuget (Feeds)",
            Description = "Retrieve the default Nuget Feed",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = true,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static string GetDefaultFeed()
        {
            return NugetClientAddIn.NugetFeedManager.Clients.First(x => x.Value == NugetClientAddIn.NugetFeedManager.DefaultClient).Key;
        }

        /// <summary>
        /// Retrieve a list of registered feeds.
        /// </summary>
        /// <returns>A list of registered feeds.</returns>
        [ExcelFunction(
            Name = "nugetListFeeds",
            Category = "Nuget (Feeds)",
            Description = "List Nuget Feeds",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListFeeds()
        {
            return NugetClientAddIn.NugetFeedManager.Clients.ToRange(true, x => new
                {
                    Alias = x.Key,
                    Address = x.Value.SourceRepository.Source,
                    Root = x.Value.LocalRepository.Source
                });
        }

        #endregion

        #region Enumerate packages

        /// <summary>
        /// List available package names from a feed.
        /// 
        /// If a feed contains multiple versions with the same name, that name will only be returned once.
        /// To get version information on a specific package, use the 'nugetDescribe' worksheet function.
        /// </summary>
        /// <param name="alias">(Optional) The alias of the feed to search. Default is used when omitted.</param>
        /// <param name="search">(Optional) A query string to search in the feed. Default returns everything.</param>
        /// <param name="take">(Optional) The number of hits to return. Default returns 10. Limited to 100.</param>
        /// <param name="skip">(Optional) The number of hits to skip before the first item is returned. Default is 0</param>
        /// <returns>Available package names from a feed</returns>
        [ExcelFunction(
            Name = "nugetListAvailable",
            Category = "Nuget (List)",
            Description = "List remote packages",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListRemotePackages(string alias, int take, string search, int skip)
        {
            if (take == 0) take = 10;
            if (string.IsNullOrEmpty(search) && take > 100) return null;
            var item = NugetClientAddIn.NugetFeedManager.Get(alias);
            return item.GetRemotePackages(search)
                .Skip(skip)
                .Take(take)
                .ToList()
                .Select(x => x.Id)
                .Distinct()
                .ToRange();
        }

        /// <summary>
        /// List installed packages
        /// </summary>
        /// <param name="alias">(Optional) The alias of the feed to search. Default is used when omitted.</param>
        /// <returns>List installed packages</returns>
        [ExcelFunction(
            Name = "nugetListInstalled",
            Category = "Nuget (List)",
            Description = "List installed packages",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListInstalledPackages(string alias = "")
        {
            var item = NugetClientAddIn.NugetFeedManager.Get(alias);
            return item.GetInstalledPackages(null).ToRange(true);
        }

        /// <summary>
        /// List available updates
        /// </summary>
        /// <param name="alias">(Optional) The alias of the feed to search. Default is used when omitted.</param>
        /// <returns>List available updates</returns>
        [ExcelFunction(
            Name = "nugetListUpdates",
            Category = "Nuget (List)",
            Description = "List packages with updates",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListPackagesWithUpdates(string alias = "")
        {
            var item = NugetClientAddIn.NugetFeedManager.Get(alias);
            return item.GetPackagesWithUpdates(null).ToRange(true);
        }

        #endregion

        #region Package description

        /// <summary>
        /// Returns package metadata
        /// </summary>
        /// <param name="packageId">A package name</param>
        /// <param name="alias">(Optional) A feed alias. Default is used when omitted.</param>
        /// <returns>Package metadata</returns>
        [ExcelFunction(
            Name = "nugetDescribe",
            Category = "Nuget (Info)",
            Description = "Describe a nuget package",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] DescribePackage(string packageId, string alias = "")
        {
            if (string.IsNullOrEmpty(packageId)) return null;
            var item = NugetClientAddIn.NugetFeedManager.Get(alias);
            return item.GetRemotePackages(null)
                .Where(x => x.Id == packageId)
                .ToList()
                .ToRange(true);
        }

        /// <summary>
        /// Returns package dependencies
        /// </summary>
        /// <param name="packageId">A package name</param>
        /// <param name="alias">(Optional) A feed alias. Default is used when omitted.</param>
        /// <returns>Returns package dependencies</returns>
        [ExcelFunction(
            Name = "nugetGetDependencies",
            Category = "Nuget (Info)",
            Description = "List Nuget package dependencies",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListPackageDependencies(string packageId, string alias = "")
        {
            if (string.IsNullOrEmpty(packageId)) return null;
            var item = NugetClientAddIn.NugetFeedManager.Get(alias);
            return item.GetRemotePackages(null)
                .Where(x => x.Id == packageId)
                .ToList()
                .SelectMany(x => x.Dependencies.Select(y => new { y.Id, y.VersionSpec }))
                .ToRange(true);
        }

        /// <summary>
        /// Returns .Net assembly references defined in a package
        /// </summary>
        /// <param name="packageId">A package name</param>
        /// <param name="alias">(Optional) A feed alias. Default is used when omitted.</param>
        /// <returns>Returns .Net assembly references defined in a package</returns>
        [ExcelFunction(
            Name = "nugetGetReferences",
            Category = "Nuget (Info)",
            Description = "List Nuget package references",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListPackageReferences(string packageId, string alias = "")
        {
            if (string.IsNullOrEmpty(packageId)) return null;
            var item = NugetClientAddIn.NugetFeedManager.Get(alias);
            return item.GetRemotePackages(null)
                .Where(x => x.Id == packageId)
                .ToList()
                .SelectMany(x => x.AssemblyReferences.Select(y => y.Name).Union(x.FrameworkAssemblies.Select(y => y.AssemblyName)))
                .ToRange();
        }

        /// <summary>
        /// Returns tags in a package
        /// </summary>
        /// <param name="packageId">A package name</param>
        /// <param name="alias">(Optional) A feed alias. Default is used when omitted.</param>
        /// <returns>Tags in a package</returns>
        [ExcelFunction(
            Name = "nugetGetTags",
            Category = "Nuget (Info)",
            Description = "List Nuget package tags",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListPackageTags(string packageId, string alias = "")
        {
            if (string.IsNullOrEmpty(packageId)) return null;
            var item = NugetClientAddIn.NugetFeedManager.Get(alias);
            return item.GetRemotePackages(null)
                .Where(x => x.Id == packageId)
                .ToList()
                .Select(x => x.Tags)
                .ToRange();
        }

        /// <summary>
        /// Returns package authors
        /// </summary>
        /// <param name="packageId">A package name</param>
        /// <param name="alias">(Optional) A feed alias. Default is used when omitted.</param>
        /// <returns>Package authors</returns>
        [ExcelFunction(
            Name = "nugetGetAuthors",
            Category = "Nuget (Info)",
            Description = "List Nuget package authors",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[,] ListPackageAuthors(string packageId, string alias = "")
        {
            if (string.IsNullOrEmpty(packageId)) return null;
            var item = NugetClientAddIn.NugetFeedManager.Get(alias);
            return item.GetRemotePackages(null)
                .Where(x => x.Id == packageId)
                .ToList()
                .SelectMany(x => x.Authors)
                .ToRange();
        }

        #endregion

        #region Package management

        /// <summary>
        /// Installs a package
        /// </summary>
        /// <param name="packageId">A package name</param>
        /// <param name="version">A package version</param>
        /// <param name="acceptLicense">(Optional) Set to true to accept license agreement. Default is false.</param>
        /// <param name="alias">(Optional) A feed alias. Default is used when omitted.</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "nugetInstall",
            Category = "Nuget (Management)",
            Description = "Install a Nuget package",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object InstallPackage(string packageId, string version, bool acceptLicense, string alias = "")
        {
            if (string.IsNullOrEmpty(packageId)) return null;
            var feed = NugetClientAddIn.NugetFeedManager.Get(alias);
            var idMatches = feed.GetRemotePackages(null)
                .Where(x => x.Id == packageId)
                .ToList();

            // Check for package Id match
            if (idMatches.Count == 0)
            {
                return "Failed: PackageId not found.";
            }

            var versionMatched = idMatches
                .Where(x => x.Version.ToString() == version)
                .FirstOrDefault();

            // Check for version match
            if (versionMatched == null)
            {
                return "Failed: Could not find package version " + version;
            }

            // Check if package is already installed
            if (feed.IsPackageInstalled(versionMatched))
            {
                return "Failed: Package already installed";
            }

            // Check for license acceptance
            if (versionMatched.RequireLicenseAcceptance && !acceptLicense)
            {
                return "Failed: Package installation required license acceptance.";
            }

            // Install package and get errors
            var results = feed.InstallPackage(versionMatched);

            // If there were errors, return them
            if (results.Count() > 0)
            {
                return results.ToRange();
            }
            
            return "Success: Download number " + (versionMatched.DownloadCount + 1);
        }

        /// <summary>
        /// Uninstalls a package
        /// </summary>
        /// <param name="packageId">A package name</param>
        /// <param name="version">A package version</param>
        /// <param name="removeDependencies">(Optional) Set to true to remove dependencies</param>
        /// <param name="alias">(Optional) A feed alias. Default is used when omitted.</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "nugetUninstall",
            Category = "Nuget (Management)",
            Description = "Uninstall a Nuget package",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object UninstallPackage(
            string packageId, 
            string version, 
            bool removeDependencies, 
            string alias = "")
        {
            if (string.IsNullOrEmpty(packageId)) return null;
            var feed = NugetClientAddIn.NugetFeedManager.Get(alias);
            var idMatches = feed.GetInstalledPackages(null)
                .Where(x => x.Id == packageId)
                .ToList();

            // Check for package Id match
            if (idMatches.Count == 0)
            {
                return "Failed: PackageId not found.";
            }

            var versionMatched = idMatches
                .Where(x => x.Version.ToString() == version)
                .FirstOrDefault();

            // Check for version match
            if (versionMatched == null)
            {
                return "Failed: Could not find package version " + version;
            }

            // Uninstall package and get errors
            IEnumerable<string> results;
            try
            {
                results = feed.UninstallPackage(versionMatched, removeDependencies);
            } catch (Exception e)
            {
                return e.Message;
            }

            // If there were errors, return them
            if (results.Count() > 0)
            {
                return results.ToRange();
            }

            return "Success: Package uninstalled";
        }

        /// <summary>
        /// Update a package
        /// </summary>
        /// <param name="packageId">A package name</param>
        /// <param name="version">A package version</param>
        /// <param name="alias">(Optional) A feed alias. Default is used when omitted.</param>
        /// <returns>Success/Failure</returns>
        [ExcelFunction(
            Name = "nugetUpdate",
            Category = "Nuget (Management)",
            Description = "Update a Nuget package",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object UpdatePackage(string packageId, string version, string alias = "")
        {
            if (string.IsNullOrEmpty(packageId)) return null;
            var feed = NugetClientAddIn.NugetFeedManager.Get(alias);
            var idMatches = feed.GetPackagesWithUpdates(null)
                .Where(x => x.Id == packageId)
                .ToList();

            // Check for package Id match
            if (idMatches.Count == 0)
            {
                return "Failed: PackageId not found.";
            }

            var versionMatched = idMatches
                .Where(x => x.Version.ToString() == version)
                .FirstOrDefault();

            // Check for version match
            if (versionMatched == null)
            {
                return "Failed: Could not find package version " + version;
            }

            // Update package and get errors
            IEnumerable<string> results;
            try
            {
                results = feed.UpdatePackage(versionMatched);
            } catch (Exception e)
            {
                return e.Message;
            }

            // If there were errors, return them
            if (results.Count() > 0)
            {
                return results.ToRange();
            }

            return "Success: Package updated";
        }


        #endregion

    }
}